/**
 *	The NeuroCoSA Toolkit
 *	Copyright (C) 2003-6 Stuart Meikle.
 *
 *	This is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Lesser General Public
 *	License as published by the Free Software Foundation; either
 *	version 2.1 of the License, or (at your option) any later version.
 *
 *	This library is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *	Lesser General Public License for more details.
 *
 * @author	Stuart Meikle
 * @version	2006-halloween(mk2)
 * @license	LGPL
 */

package org.stumeikle.NeuroCoSA;
import org.stumeikle.NeuroCoSA.NIS.*;
import java.util.*;

import java.awt.*;
import java.awt.event.*;
import java.lang.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
import javax.swing.event.*;
import org.stumeikle.NeuroCoSA.AutoCluster.NScalar;

//20051013	stumeikle big changes to this class for surprise and expectation
//	    	one major consequence ==> simple synapses can't be used for synapse- synapse
//		connections, we'll probalby need a new class for that.

/** 
 * Simple Synapse
 * 
 * A new base class for the synapses. Implements reviewBehaviour.
 * This entails watching for when this synapse propagates a DRIVE vesicle
 * and then later checking if that drive vesicle was driving the 
 * winning drive neuron in the output lobe
 *
 * stumeikle 20050715
 *
 */
public class	SimpleSynapse	extends Synapse implements SimpleBackPropAPI
{
	//private	boolean		iProppedDrive;
	
	/** stumeikle 20051009 prototype expectation code
	 */
// 	private long		iUpdateTimeDelta;
	private STLTMemory	iExpectedResponseTime;
	private double		iDriveTimeout;
				/** how long to wait on our (accepted) drive working ?
				 *  related to expectation time
				 */
	private long		iDriveAcceptTimeout;
				/** how long do we wait before giving up trying to get control of
				 *  the body ? this is related to the number of layers in the 
				 *  brain.
				 */
	public  static final int normal_operation = 0;
	public  static final int drive_requested = 1;
	public  static final int drive_accepted = 2;
	private int		iDriveState;
				/** original idea -> no states in synapses. but today we
				 *  must change. possible states are: normal, drive_requested
				 *  drive_accepted. 
				 */
	private long		iDriveTime;
	private long		iAcceptTime;
	private boolean		iParentDriving;
	private Layer		iOutputNeuronsLayer;/**temp vars used to prevent repeated actions*/
	private Neuron		iOutputNeuron;/**temp vars used to prevent repeated actions*/
	

	public	double	getDriveTimeout()
	{ return iDriveTimeout; }
	public	double	getDriveAcceptTimeout()
	{ return iDriveAcceptTimeout; }
	public	int	getDriveState()
	{ return iDriveState; }
	public	long	getDriveTime()
	{ return iDriveTime; }
	public	long	getDriveAcceptTime()
	{ return iAcceptTime; }

	public     SimpleSynapse(Neuron p,Neuron n)
    	{
            super(p,n);
	    //iProppedDrive = false;
	    
	    //Modified stumeikle 20060606
	    //call a static method in simple brain to register all synapses
	    SimpleBrain.registerSynapse(this);
	    iExpectedResponseTime = new STLTMemory();
 	    iDriveTimeout = calcTimeoutFromExTime();// we time out after this period of time
	    iDriveAcceptTimeout = 20; 
	    iDriveState = normal_operation;
	    iDriveTime = 0;
	    iAcceptTime = 0;
	    iOutputNeuron = getOutputNeuron();
	    if (iOutputNeuron!=null)   	iOutputNeuronsLayer = iOutputNeuron.getLayer();
	}

	/**
 	 * Overload the base class destructor to ensure that we are removed from the backprop list
	 */
	public	void	destructor()
	{
	    SimpleBrain.deregisterSynapse(this);
	    super.destructor();
	}
	
/* ADD
	public	void	setUpdateTimeDelta( long l )
	{ iUpdateTimeDelta = l; }
*/

	/** Take the given vesicle package and create a vesicle which is to be propagated out of the
     	 *  synapse. This is a virtual method and should be overloaded by the users specific synapse
     	 *  implementation. Here we call a user method and add extra functionality for expectation
     	 * @param vp the vesicle pacakge to process
     	 */
    	protected final Vesicle	propagate(Vesicle vp )  //prevent further overloading
    	{
	    Vesicle	v;
	    int		s;
	    
	    iParentDriving = false;
	    if (vp!=null)
	    {
	    	v = simplePropagate(vp);
		if (v!=null)
		{
		    if (v.getType()==Vesicle.DRIVE_VESICLE ||
		        v.getType()==Vesicle.PTR_DRIVE_VESICLE )
	    	    { iParentDriving = true; }
		}
	    }
	    else
	    	v = null;
	        
	    //now process v and set our state and expectation and so on .
	    switch( iDriveState )
	    {
	    	case 	normal_operation://switch to drive state if nec
					 if ( iParentDriving == true )
					 {
					     iDriveState = drive_requested;
					     iDriveTime = getParent().getLayer().getBrainTime();
					 }
					 break;
		case	drive_requested:
					//wait on drive acceptance.
					//System.out.println("Synapse, waiting on drive acceptance");
					stateDriveRequested(false);
		  		        //if nothing received after time T... (covered above)
	    				//if reset signal received. ... (covered below, in reset())
	    				//if neuron stops driving...
	    				if ( iParentDriving == false )
	    				{
System.out.println("FASTFEEDBACK:" + this + ": drive reset 5");
	    				    resetToNormalState();
	    				}
					break;
		case	drive_accepted: //wait on drive completion
					//System.out.println("Synapse, waiting on drive completion");
					stateDriveAccepted();
					break;
	    }
	    	    
	    return v;
        }
	
	/** (New method, stumeikle 20060606)
  	 *  Propagate drive status information back up through the network.
	 *  Do not prop. drive *success* information 
	 */
	public	final void	backPropagate()
    	{	        
	    //now process v and set our state and expectation and so on .
	    switch( iDriveState )
	    {

		case	drive_requested:
					//wait on drive acceptance.
					//System.out.println("Synapse, waiting on drive acceptance");
					stateDriveRequested(true); //No time delta for back prop ->
								//all updates are complete.
					break;

		//don't process this information when back propagating -> the neuron can decide on 
		//success or failure during a normal (forward prop) update cycle
		//case	drive_accepted: //wait on drive completion
		//			//System.out.println("Synapse, waiting on drive completion");
		//			stateDriveAccepted();
		//			break;
		default:	 	break;
	    }
        }

	/** state methods
	 */
	private	    void	stateDriveRequested(boolean backprop)
	{
	    //see use-cases doc. 
	    // begin to wait on drive acceptance.
	    // stop waiting if:
	    // 1 acceptance received
	    // 2 failed received
	    // 3 nothing received after time T
	    // 4 <neuron stops driving?>
	    // 5 reset signal received
	    int s=SimpleNeuron.drive_status_undefined;
	    SimpleNeuron	n=null;
	    Synapse		c;

	    InfoWithSingleStore	a = (InfoWithSingleStore) iOutputNeuronsLayer.
							getInfoService().findInfo("WinningDrive");
	    if (a!=null)
	    {
		if (backprop==false)
		    n = (SimpleNeuron) a.getValue(Info.last_update_time);
		else
		    n = (SimpleNeuron) a.getValue(Info.now); // during backprop ignore sync
	    }

	    if (n!=null)
		s = n.getDriveStatus();
//------ (prev line) CHANGE THIS ----------------------------------- 
//MARK ONE
	    
	    a = (InfoWithSingleStore) iOutputNeuronsLayer.getInfoService().findInfo( "DriveController" );
	    if (backprop==false)
	        c = (Synapse) a.getValue(Info.last_update_time);
	    else
		c = (Synapse) a.getValue(Info.now); //during backprop ignore sync
				
	    if (n != iOutputNeuron )	
	    {
	    	//if the winner in the output lobe was NOT the neuron that we're connected to
		//forget waiting etc. 
System.out.println("FASTFEEDBACK:" + this +": drive not accepted 1. output neuron = " + iOutputNeuron + " winner =" +  n);
		getSNParent().notifyDriveNotAccepted();
	    	resetToNormalState();
		return;
	    }
				
	    switch(s)
	    {
	    	case SimpleNeuron.drive_status_undefined:	
			//wot to do here ? just continue waiting ?
		case SimpleNeuron.drive_status_waiting:
			//output neuron is waiting on response. we will wait too,
			//up to a point. 
			if ( getParent().getLayer().getBrainTime() - iDriveTime > iDriveAcceptTimeout )
			{
			    //too long. 
			    //become 'bored' waiting on the response. 
			    //possibly signal boredom to lobe. reset to normal
			    //notify neuorn of failure thru no- accept/reject. 
System.out.println("FASTFEEDBACK:" + this + ": drive not accepted 2");
			    resetToNormalState();
			    getSNParent().notifyDriveNotAccepted();
			} 
			else
			{
			    //continue waiting ......
			}
			break;
		case SimpleNeuron.drive_status_accepted:	
			//notify the neuron
			//state change DRIVEREQ-DRIVEACC
			
//20060207 MARK ONE
//			----> 20060205 stumeikle. Don't just notify Drive Accepted
//			----> here if the output is accepted. We can only be 
//			----> drive accepted if we control the output neuron.
//			----> check taht we are the owner of the driven neuron
if (backprop==true)
System.out.println("DEBUG CONTROLLER: feedbak controller = " + c );

			if ((c==this && n.drivesMustCompete()) || n.drivesMustCompete()==false)
			{
			    System.out.println("DEBUG CONTROLLER:" + this + " we are the controller, " + 
			    		       "propagating acceptance");
System.out.println("FASTFEEDBACK:"+this + ": drive accepted 1");
    			    getSNParent().notifyDriveAccepted();
 			    iDriveState=drive_accepted;
			    iAcceptTime=getParent().getLayer().getBrainTime();
			    //start the clock. wait on drive completion

			}else
			{
			    System.out.println("DEBUG CONTROLLER: " + this + "we are NOT the controller, propagating nowt");
			    //we are not the controller or not yet - return to waiting state
			    //or say drive failed ?
     			    if ( getParent().getLayer().getBrainTime() - iDriveTime > iDriveAcceptTimeout )
   			    {
			    	//too long. 
				//become 'bored' waiting on the response. 
				//possibly signal boredom to lobe. reset to normal
				//notify neuorn of failure thru no- accept/reject. 
System.out.println("FASTFEEDBACK:" + this + ": drive not accepted 3");
				resetToNormalState();
				getSNParent().notifyDriveNotAccepted();
			    } 
			    else
			    {
			    	//continue waiting ......
			    }
			}
			
			break;
		case SimpleNeuron.drive_status_rejected:
			//notify the neuron and reset 
			//** would also be possible here to say, only revert to normal mode if 
			//** drive rejected and we were not the controller
System.out.println("FASTFEEDBACK:" + this + ": drive not accepted 4");
			getSNParent().notifyDriveNotAccepted();
			resetToNormalState();
			break;
	    }
	}
	
	private	    double	calcTimeoutFromExTime()
	{
	    NScalar		s = (NScalar) iExpectedResponseTime.getCentroid();
	    
	    if (s!=null)
	    {
		if (s.getValue() > 0.0)
	       	    return s.getValue() * 5.0; 
	        else
	    	    return Double.MAX_VALUE;
	    }
	    
    	    return Double.MAX_VALUE;
	}
	
	private	    void	resetToNormalState()
	{
	    iDriveState=normal_operation;
	    iDriveTime=0;
	    iAcceptTime=0;
	}
	
	public	SimpleNeuron	getSNParent()
    	{	return (SimpleNeuron)getParent(); }
	
	private	    void	stateDriveAccepted()
	{
	    double	s;
	
	    //stop waiting if:
	    //1 <neuron stops driving?>
	    //		--> should be handled automatically by neuron calling reset()
	    //2 the action is successful - neuron switches to learning mode and stops
	    //driving the synapse. synpase must be told if action successful
	    //3 action is not successful after time >> t_e , with t_e = expected time
	    //4 <failed received - case with preemption>
	    //5 reset signal received
	    
	    //we get the message of drive success from the neuron. this is different
	    //from drive acceptance. this means that the drive has sucessfully brought
	    //about the desired change. stored in simple neuron and passed in
	    
// 20060207 MARK ONE
//	    20060205 stumeikle
//	    Check again if we are the owner of the output neuron . if we are not
//	    then revert back to drive requested state. (someone else has control now,
//	    we need to wait).
	    InfoWithSingleStore	a = (InfoWithSingleStore) iOutputNeuronsLayer.
						getInfoService().findInfo("DriveController");
	    
	    Synapse 		c = null;
	    if (a!=null)	c= (Synapse)a.getValue(Info.last_update_time);

	    //transitions caused if we lose control of the output neuron:--------------------------
	    if (c!=this && ((SimpleNeuron)iOutputNeuron).drivesMustCompete())
	    {
       	        resetToNormalState();
		System.out.println("SimpleSynapse, drive superceded");
		return;
	    }

	    //(if the output stops being the winner. kind of covered implicitly in the above)
	    //
	    int ops=SimpleNeuron.drive_status_undefined;
	    SimpleNeuron	n=null;
	   
	    a = (InfoWithSingleStore) iOutputNeuronsLayer.getInfoService().findInfo("WinningDrive");
	    if (a!=null)	n = (SimpleNeuron) a.getValue(Info.last_update_time);
	    if (n!=null)        ops = n.getDriveStatus();
				
	    if (n != iOutputNeuron )	
	    {
	    	//if the winner in the output lobe was NOT the neuron that we're connected to
		//forget waiting etc. 
		getSNParent().notifyDriveNotAccepted(); 
	    	resetToNormalState();
		return;
	    }
				
	    switch(ops)
	    {
		case SimpleNeuron.drive_status_undefined:
		case SimpleNeuron.drive_status_waiting:
		case SimpleNeuron.drive_status_rejected:
				getSNParent().notifyDriveNotAccepted(); 
	    			resetToNormalState();
				break;
		case SimpleNeuron.drive_status_accepted:
				break;
	    }
	    
	    //transitions caused by the success / failure of the driving neuron (parent)-----------
	    if (getSNParent().getDriveSuccess()== SimpleNeuron.drive_succeeded)
	    {
	    	//action successful.
		//update connection strength based on success or fail.
		//also update expectation time and waiting time out
		
		//update the STLTMemory with the observed response time
		NScalar		sv = new NScalar();
		
		sv.setValue((double) (getParent().getLayer().getBrainTime() - iAcceptTime));
	    
//	        System.out.println("Surprise DEBUG6 - synapse="+ this + " and mem=" +iExpectedResponseTime);
		iExpectedResponseTime.addValue( sv );
		s=iExpectedResponseTime.calculateSurpriseFromValue( sv );
		
		strengthenConnection();
		resetToNormalState();
		System.out.println("SimpleSynapse, drive succeeded");
	        if ( s> iExpectedResponseTime.getSurpriseThreshold() )
		{
		    // drive succeeded when not expected to 
		    System.out.println("Surprise, unexpected success. Parent neuron=" + getSNParent().getLayer() );
//ADD ?		    getSNParent().getLobe().notifySignal( BrainChangeMonitor.synapse_surprised_unexpected_success,
// 		    				        s );
		}
	    }
	    if (getSNParent().getDriveSuccess()== SimpleNeuron.drive_failed)
	    {
	    	//reset state to normal. 
		//weaken conncetion strength. 
		//update expectation time if nec. and waiting time out
		
		//20060124 old code
		//s = calculateSurprise();
		//updateExpectedResponseTime(false);
		
		driveFailure();
//		weakenConnection();
		resetToNormalState();
		System.out.println("SimpleSynapse, drive failed, lobe=" + getSNParent().getLayer());
	    }
	    if (getSNParent().getDriveSuccess()== SimpleNeuron.drive_success_undefined)
	    {
	    	//waiting on drive completion
		if (getSNParent().getDriveStatus() == SimpleNeuron.drive_status_rejected)
		{
		    //parent neuron was pre-empted by another neuron , ie lost control of 
		    //the body before the drive completed
		    //reset to normal . don't change conn str
		    resetToNormalState();
		    System.out.println("SimpleSynapse, drive superceded");
		}
		else
		{
		    //wait. 
		    if (getParent().getLayer().getBrainTime() - iAcceptTime > iDriveTimeout)
		    {
		    	//our drive has had control but still hasn't produced any result...
			//reset state to normal. 
			//weaken conncetion strength. update expectation time if nec.
			//BE SURPRISED
			
			s=iExpectedResponseTime.calculateProbabilityOfMiss( );
			s=iExpectedResponseTime.calculateSurpriseFromProbability(s);

			driveFailure();
//			weakenConnection();
			resetToNormalState();
			System.out.println("SimpleSynapse, drive timedout");
			if (s > iExpectedResponseTime.getSurpriseThreshold())
			{
			    // drive expected to be successful by this time. 
// ADD?		    	    getSNParent().getLobe().notifySignal(  BrainChangeMonitor.synapse_surprised_drive_has_no_result,
// 		    				        s );
			}
		    }
		//    System.out.println("SimpleSynapse, waiting for change...");
		}		
	    }
	}
	
	//FUTURE:
	//would be good to send signals back to the neuron too to enable it to create new
	//synapses to cover shorter or longer times.  20070103 -> this needs to be Shannon theory
	/*
	private	double	calculateSurpriseFromProbability(double p)
	{
	    double s;
	    
	    s = 1.0 / (p + 0.001);
	    
	    //debug
	    if ( s> 1.0/iExpectedResponseTime.getFractionInside() )
	    {
	    	NScalar		 n = (NScalar) iExpectedResponseTime.getCentroid();
	    
	    	System.out.println("Surprise coming expected time is=" + n.getValue() + " this rt was=" + 
	    	( Brain.getTime() - iAcceptTime ) + " variance=" + iExpectedResponseTime.getVariance());
	    }
	    
	    return s;
	}*/
	
	/** This must be overloaded by the derived forms:
	 */
	public   Vesicle	simplePropagate(Vesicle vp)
	{
	    //stumeikle 20051110 - create some default behaviour.
	    //if we have a drive vesicle in the vp then propagate a drive vesicle
	    //if we have a learn vesicle then propagate a learn vesicle.
	    Vesicle	v;
	    v=vp; //.findType( Vesicle.DRIVE_VESICLE);
	    if (v!=null)	
	    {
	    	setRetSignal(vp.getSignalStrength());
	    	return v;
	    }
	
	    System.out.println("SimpleSynapse::simplePropagate>>warning, pls overload propagate!");
	    return null;
	}
//QUESTION: should we really propagate learn vesicles ? 
	
	/** another overloaded method. we need this to reset our state
	 */
	public	void	reset()
	{
	    //System.out.println("SimpleSynapse, reset");
	    
	    resetToNormalState();
	    super.reset();
	} 
}


